package com.yc.spring;

import com.yc.spring.bank.bean.Account;
import com.yc.spring.bank.bean.Record;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Stream;

public class MyAnnotationConfigApplicationContext {

    private HashMap<String, Object> ioc = new HashMap<>();

    public MyAnnotationConfigApplicationContext(Class cls){
        // 读取配置类注解
        Configuration configuration = (Configuration) cls.getAnnotation(Configuration.class);
        if(configuration==null){
            throw new RuntimeException("该类不是Spring配置类");
        }
        ComponentScan componentScan = (ComponentScan) cls.getAnnotation(ComponentScan.class);
        if(componentScan!=null){
            // 读取包扫描路径
            String[] values = componentScan.value();
            // 包扫描 ==> 组件 => new => 创建 bean ==> 加入IOC容器
            // 1. 开发模式
            packageScan(values[0]);
            // 2. jar模式 ==> jar == zip ==> javax.zip
        }
        // 扫描 @Bean 方法  => 执行 => 返回 bean ==> 加入IOC容器
        // =============
        // 实现自动注入 => 扫描每个bean内部属性, @Autowired, @Resource
    }

    // 包扫描  value => com.yc.spring
    private void packageScan(String _package) {
        // 获取类加载器
        ClassLoader classLoader = getClass().getClassLoader();
        URL url = classLoader.getResource(_package.replaceAll("\\.", "/"));
        // url ==> http://  file://  ftp://
        String path = url.getPath();

        // 廖老师的补丁
        path = path.replace("%e6%96%87%e4%bb%b6%e6%9f%9c","文件柜");
        // 廖老师的补丁
        File dir = new File(path);
        System.out.println(dir);
        // com.yc.spring.bean.Record
        // ==> F:\_文件柜\yc118\C118-S3-Ply-spring\target\classes\com\yc\spring\bean\Record.class
        // 使用递归扫描
        // 1.定义一个递归方法
        // 2. 参数一: 要扫描的路径, 参数二: 收集文件集合
        // 3. 递归的退出
        List<File> fileList = new ArrayList<>();
        scanClassFile(dir, fileList);
        fileList.forEach(f-> {
            System.out.println(f);
            // 将文件路径 ==> 类路径
            String classpath = f.getAbsolutePath().
                    replaceAll(".+\\\\classes\\\\(.+)\\.class","$1");
            classpath = classpath.replaceAll("\\\\",".");

            // 读取类上的注解, 判断是否是组件
            try {
                Class cls = Class.forName(classpath);
                if(cls.getAnnotation(Component.class) != null ||
                        cls.getAnnotation(Controller.class) != null ||
                        cls.getAnnotation(Service.class) != null ||
                        cls.getAnnotation(Repository.class) != null){
                    // 创建对象
                    Object bean = cls.newInstance();
                    String name = "";
                    if(cls.getAnnotation(Component.class)!=null) {
                        Component c = (Component) cls.getAnnotation(Component.class);
                        name = c.value();
                    }
                    if(cls.getAnnotation(Controller.class)!=null) {
                        Controller c = (Controller) cls.getAnnotation(Controller.class);
                        name = c.value();
                    }
                    if(cls.getAnnotation(Service.class)!=null) {
                        Service c = (Service) cls.getAnnotation(Service.class);
                        name = c.value();
                    }
                    if(cls.getAnnotation(Repository.class)!=null) {
                        Repository c = (Repository) cls.getAnnotation(Repository.class);
                        name = c.value();
                    }

                    // 如果是 组件 那么, 创建并加入到 ioc 容器中
                    if(name.isEmpty()){
                        // 未配置了名字
                        name = cls.getSimpleName();
                        // 默认名是类名首字母小写
                        name = name.substring(0,1).toLowerCase() + name.substring(1);
                    }
                    // 添加到 IOC 容器中 ==> Map
                    ioc.put(name, bean);

                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("类未找到",e);
            } catch (InstantiationException e) {
                throw new RuntimeException("构造函数内部异常",e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("访问异常, private构造方法...",e);
            }

        });


    }

    private void scanClassFile(File dir, List<File> fileList) {
        for (File file : dir.listFiles()) {
            if(file.isFile()){
                fileList.add(file);
            } else if(file.isDirectory()){
                scanClassFile(file, fileList);
            }
        }
    }

    public <T> T getBean(Class<T> cls){
        // 获取 map 集合的 值集合
        // ioc.keySet(); 键集合  ==>  bean 的 id
        Collection<Object> values = ioc.values();  //  值集合 bean
        // 集合的流语法 stream  ==> filter.过滤  => toArray 转数组
        Object[] beans = values.stream()
                .filter(b -> b.getClass().equals(cls))
                .toArray();
        if(beans.length == 0){
            throw new RuntimeException("容器中没有该 bean : " + cls);
        } else if(beans.length == 1){
            return (T) beans[0];
        } else {
            // 过滤出 主bean
            beans = Stream.of(beans)
                    .filter(b->b.getClass().getAnnotation(Primary.class) != null)
                    .toArray();
            if(beans.length == 0){
                throw new RuntimeException("容器中配置了多个该类型的 bean, 但是没有配置主bean : " + cls);
            } else if(beans.length == 1){
                return (T) beans[0];
            } else {
                throw new RuntimeException("容器中配置了多个该类型的 bean, 但是配置了多个主bean : " + cls);
            }
        }
    }

    public <T> T getBean(String id){
        Object o = ioc.get(id);
        if (o==null){
            throw new RuntimeException("容器中没有该 bean : " + id);
        }
        return (T) o;
    }

    public static void main(String[] args) {
        MyAnnotationConfigApplicationContext cxt = new MyAnnotationConfigApplicationContext(BeanConfig.class);
        Account acc = cxt.getBean("account");
        System.out.println(acc);

        Record bean = cxt.getBean(Record.class);
        System.out.println(bean);
    }


}
